package blog.network;

import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

import blog.*;
import blog.gui.*;
import blog.db.*;

/**
 * A class for maintaining state of a connection with the remote XML-RPC blog server.
 * This class calls synchronously into the XMLRPC client:
 * callers should deal with showing UI status and so forth in a separate thread.
 * 
 */
public class BlogEngine
{
	//This is the Blogger app key provided for KABLOG in general: right now
	//both the Series 60 version and the J2ME version share the same appkey.
	//If this becomes a problem in the future, we may need separate keys.
	private static final String sAppKey = "D356E2500FAD10C5664D07CC8790E7A807776BE3";

	private Vector callParms;

	protected XmlRpcClient2 netClient;

	//there are three general levels of support:
	//best: Full MovableType 2.6 or above support
	//good: MetaWeblog support
	//okay: blogger API support only

	//asynchronous operations 
	protected int topLevelProgress;

	protected Object asyncResult;

	protected Object asyncParms;

	protected int asyncOpType;

	//published async method codes
	public static final int POST_SIMPLE = 2; //post a simple body-only entry

	public static final int POST_ENTRY = 4; //post one EntryRecord

	public static final int POST_MULT = 8; //post multiple entries

	public static final int GET_BLOGSLIST = 16; //get the list of blogs the user can access

	public static final int GET_SUPP_METHODS = 32; //get a list of supported xmlrpc methods

	private static BlogEngine _instance;
	public synchronized static BlogEngine getInstance()
	{
		if ( _instance==null )
		{
			_instance = new BlogEngine();
		}
		return _instance;
	}
	
	private BlogEngine()
	{
	}

	/**
	 * Set up the KXmlRpcClient 
	 * @param the URL to use to access the XMLRPC server
	 */
	protected void initNetClient(String targetURL)
	{
		if (netClient == null)
		{
			//create new KXmlRpcClient
			netClient = new XmlRpcClient2( targetURL );
		}
		else
		{
			//reuse existing KXmlRpcClient
			netClient.setTargetURL( targetURL );
			netClient.clearConn();
		}
	}//initNetClient

//	/**
//	 * Post multiple entries, and optionally remove them from the db when
//	 * posted successfully.
//	 * @param 
//	 * @param Delete entries from local cache DB when finished
//	 */
//	public void postMultipleEntries(Vector entryIdList, boolean deleteAfterPost)
//	{
//		for (int i = 0; i < entryIdList.size( ); i++)
//		{
//			try
//			{
//				Integer recID = (Integer) entryIdList.elementAt( i );
//				EntryRecord curEntry = (EntryRecord) entriesDB.getEntryByID( recID.intValue( ) );
//				postEntry( curEntry );
//				entriesDB.removeEntryByID( curEntry.getRecID( ) );
//			}
//			catch (KDbException dbEx)
//			{
//				break; //quit trying to post entries at this point!
//			}
//			catch (IOException ioEx)
//			{
//				asyncResult = ioEx;
//				break; //quit trying to post entries at this point!
//			}
//			catch (KXmlRpcFault fault)
//			{
//				asyncResult = fault;
//				break; //quit trying to post entries at this point!
//			}
//		}
//	}//postMultipleEntries

	/**
	 * Post an entry including a title and creation date
	 * @param The entry to be posted to the blog server
	 */
	public void postEntry(BlogEntry entry, IBaseGui waitGui) throws IOException, XmlRpcFault
	{

		Date creationDate = new Date(entry.getCreatedDate());

		String entryBody = null;
		
		this.initNetClient(SysRuntime.curAccount.getFullUrl());
		netClient.setWaitGui(waitGui);

		if ((SysRuntime.curAccount.isMetaWeblog()) || 
				(SysRuntime.curAccount.isMovableType()))
		{

			if (entry.getMediaData() != null)
			{
				if ((entry.getMediaUrl() == null) || (entry.getMediaUrl().length( ) < 3))
				{
					//need to post the media to the server

					// By default we always post media to the "media/" directory:
					// this centralizes the location of all blog media files.  Otherwise,
					// Movable Type &co. will put the media files in some arbitrary place.
					Object mediaResult = this.postMediaObject( entry );

					if (mediaResult instanceof String)
					{
						entry.setMediaUrl( (String) mediaResult );
						BlogEntryDB.update(entry);
					}
					else
					{
						throw new XmlRpcFault( netClient.getFailureCode( ), "Couldn't upload media." );
						//proceed no further
					}
				}

				if (entry.getMediaUrl() != null)
				{
//					String phoneModel = System.getProperty( "microedition.platform" );
//					if (null == phoneModel)
//						phoneModel = "Generic_Phone";
//
//					int firstSlashPos = phoneModel.indexOf( "/" );
//					if (-1 != firstSlashPos)
//					{
//						phoneModel = phoneModel.substring( 0, firstSlashPos );
//					}

					entryBody = "<br/><img src=\"" + entry.getMediaUrl() + "\" alt=\"MYBLOG\"><br/><br/>\n" + entry.getBody();
				}
			}
			else
			{
				initNetClient( SysRuntime.curAccount.getFullUrl() );
				entryBody = entry.getBody();
			}

			//build the content struct
			Hashtable content = new Hashtable( 5 );
			content.put( "title", entry.getTitle() );
			content.put( "description", entryBody );
			content.put( "dateCreated", creationDate );
			if (SysRuntime.curAccount.isMovableType() )
			{
				content.put( "mt_allow_comments", new Boolean( entry.isAllowComments() ) );// settable by user
				content.put( "mt_convert_breaks", new Boolean( entry.isConvertBreaks() ) );// settable by user
			}

			callParms = new Vector( 5 );
			callParms.addElement( SysRuntime.curAccount.getBlogId() );
			callParms.addElement( SysRuntime.curAccount.getUserName() );
			callParms.addElement( SysRuntime.curAccount.getPassword() );
			callParms.addElement( content );
			callParms.addElement( new Boolean( entry.isPublish() ) ); //settable by user

			Object retVal = netClient.remoteCall( XmlRpcClient2.RPCNAME_POST_FULL_ENTRY, callParms );

			callParms = null;
		}
		else
		{
			initNetClient( SysRuntime.curAccount.getFullUrl() );
			postNewSimpleEntry( entry );
		}
	}//postEntry

	/**
	 * Post a new simple blog entry (body only, no title and so forth)
	 * using the connection info from prefs.
	 */
	public void postNewSimpleEntry(BlogEntry entry) throws IOException, XmlRpcFault
	{
		callParms = new Vector( 6 );

		callParms.addElement( sAppKey );
		callParms.addElement( SysRuntime.curAccount.getBlogId() );
		callParms.addElement( SysRuntime.curAccount.getUserName() );
		callParms.addElement( SysRuntime.curAccount.getPassword() );
		callParms.addElement( entry.getTitle() + "\n\n" + entry.getBody() );
		callParms.addElement( new Boolean( entry.isPublish() ) );

		// Object retVal = 
		netClient.remoteCall( XmlRpcClient2.RPCNAME_POST_SIMPLE_ENTRY, callParms );

		callParms = null;

	}//postNewSimpleEntry

	/**
	 metaWeblog.newMediaObject
	 Description: Uploads a file to your webserver. 
	 Parameters: String blogid, String username, String password, struct file
	 Return value: URL to the uploaded file.
	 Notes: the struct file should contain two keys: base64 bits (the base64-encoded contents of the file) and String name (the name of the file). The type key (media type of the file) is currently ignored.
	 */
	public Object postMediaObject( BlogEntry entry ) throws IOException, XmlRpcFault
	{
		byte[] rawData = entry.getMediaData();
		String mimeDataType = entry.getMediaType();
		String objectName = "media/" + entry.getMediaName();
			
		Object retVal = null;
		initNetClient( SysRuntime.curAccount.getFullUrl() );

		//we only allow posting of media objects if the server supports it!
		if ((SysRuntime.curAccount.isMovableType()) || (SysRuntime.curAccount.isMetaWeblog()))
		{
			//build the content struct
			//<bits>, <name>, <type>
			Hashtable content = new Hashtable( 3 );
			content.put( "bits", rawData );
			content.put( "name", objectName );
			content.put( "type", mimeDataType );

			callParms = new Vector( 4 );
			callParms.addElement( SysRuntime.curAccount.getBlogId() );
			callParms.addElement( SysRuntime.curAccount.getUserName() );
			callParms.addElement( SysRuntime.curAccount.getPassword() );
			callParms.addElement( content );

			retVal = netClient.remoteCall( XmlRpcClient2.RPCNAME_POST_MEDIA_OBJ, callParms );

			if (retVal instanceof String)
			{
			}
			else if (retVal instanceof Hashtable)
			{
				//struct returned
				String newURL = (String) ((Hashtable) retVal).get( "url" );
				
				//FOUND A BUG HERE, MOVABLE_TYPE BLOG COULDN'T GET TRUE ADDRESS
				if ( SysRuntime.curAccount.isMovableType() )
				{
					int pos = newURL.indexOf("/blogmedia/");
					if ( pos!=-1 )
					{
						StringBuffer buf = new StringBuffer();
						buf.append(newURL.substring(0, pos)).append("/blog/media/").append(newURL.substring(pos+"/blogmedia/".length()));
						newURL = buf.toString();
					}
				}
				
				retVal = newURL;
			}
			else
			{
				int rc = 0;
				if (retVal == null)
				{
					rc = netClient.getFailureCode( );
				}
			}

			callParms = null;
		}

		return retVal;

	}//postMediaObject

//	/**
//	 *
//	 */
//	public void getUsersBlogsListAsync()
//	{
//		try
//		{
//			asyncResult = getUsersBlogsList( );
//		}
//		catch (KXmlRpcFault fault)
//		{
//			asyncResult = fault;
//		}
//		catch (IOException ioEx)
//		{
//			asyncResult = ioEx;
//		}
//
//	}//getUsersBlogsListAsync
	
	public Vector getUsersBlogsList( BlogAccount account, IBaseGui waitGui ) throws IOException, XmlRpcFault
	{
		callParms = new Vector( 3 );
		initNetClient( account.getFullUrl() );

		callParms.addElement( sAppKey );
		callParms.addElement( account.getUserName() );
		callParms.addElement( account.getPassword() );
		
		netClient.setWaitGui(waitGui);

		Vector blogList = (Vector) netClient.remoteCall( XmlRpcClient2.RPCNAME_GET_USERS_BLOGS, callParms );

		callParms = null;

		return blogList;
	}

	/**
	 * Get the list of blogs for which this user has write privileges.
	 * @param sUsername  The user's username. (e.g. "tstellanova" in mt)
	 * @param sPassword  The user's password (in cleartext).
	 */
	public Vector getUsersBlogsList( IBaseGui waitGui ) throws IOException, XmlRpcFault
	{
		return getUsersBlogsList(SysRuntime.curAccount, waitGui);
	}//getUsersBlogsList

	/**
	 * Get the list of XMLRPC methods the blog server supports
	 */
	/*
	 public Vector getSupportedMethods() throws IOException, KXmlRpcFault {
	 KablogPrefs prefs = prefsDB.getKablogPrefsRecord();

	 callParms  = null;
	 initNetClient(prefs.sURL);
	 String methDump = "Methods: ";

	 Vector methsList = (Vector)netClient.remoteCall("mt.supportedMethods",callParms);
	 
	 
	 for (int i = 0; i < methsList.size(); i++) {
	 String meth = (String)methsList.elementAt(i);
	 if (meth != null) {
	 methDump += meth + "\n";
	 }
	 }
	 
	 callParms = null;
	 return methsList;


	 }//getSupportedMethods
	 */

	//Methods for supporting KNetworkClient interface
	/**
	 * @return The progress of this item, from 0 to 100 
	 */
	public int getProgress()
	{
		if (netClient != null)
		{
			if (topLevelProgress > 0)
				return topLevelProgress;
			else return netClient.getProgress( );
		}
		else return 0;//TODO update with valid progress value?
	}//getProgress

	/**
	 * @ return Zero if no failure, or the nonzero error code;
	 */
	public int getFailureCode()
	{
		int fcode = 0;

		if (this.asyncResult != null)
		{
			if (asyncResult instanceof XmlRpcFault)
			{
				fcode = ((XmlRpcFault) asyncResult).code;
				if (fcode == XmlRpcFault.UNAUTHORIZED)
					fcode = ProgressStatus.ERR_BAD_USER;

			}
			else if (asyncResult instanceof Exception)
			{
				fcode = ProgressStatus.ERR_IO;
			}
		}
		else if (netClient != null)
		{
			fcode = netClient.getFailureCode( );
		}

		return fcode;
	}//getFailureCode

	/**
	 * @return True if operation can be cancelled, false if not
	 */
	public boolean isCancellable()
	{
		return false; //none of the operations can be cancelled currently 
	}//isCancellable

	/**
	 * Set up an asynchronous operation to execute as soon as
	 * this client's run method is called.
	 * @param The type of operation to execute. This will be implementation-specific.
	 * @param Parameters to help execute the asynchronous operation
	 */
	public void setupAsyncOp(int type, Object parms)
	{
		asyncOpType = type;
		asyncParms = parms;
		asyncResult = null;
		topLevelProgress = 0;
	}

//	/**
//	 * Run the current network-bound operation (in a separate thread).
//	 */
//	public void run()
//	{
//
//		switch (asyncOpType)
//		{
//		case (POST_MULT):
//		{
//			Vector idList = (Vector) (((Vector) asyncParms).elementAt( 0 ));
//			boolean doDeleteAfterPost = ((Boolean) ((Vector) asyncParms).elementAt( 1 )).booleanValue( );
//			postMultipleEntries( idList, doDeleteAfterPost );
//			topLevelProgress = 100;
//		}//POST_MULT
//			break;
//
//		case (GET_BLOGSLIST):
//			getUsersBlogsListAsync( );
//			topLevelProgress = 100;
//			break;
//		}
//	}//run

	public void reset()
	{
		netClient.reset( );
		asyncOpType = 0;
		asyncParms = null;
		asyncResult = null;
	}

	public void cancel()
	{ //XXX No op for now?
		if ((netClient != null) && netClient.isCancellable( ))
			netClient.cancel( );
	}//cancel

	/**
	 *
	 */
	public Object getAsyncResult()
	{
		return asyncResult;
	}

}